import re
from logging import getLogger
from typing import List, Optional

from tokens import Token, ParagraphType, QuestionToken, OptionToken, IgnoreToken, OpticalLine
from utils import replace_chinese_punctuation


class QuestionEnum:
    QUESTION_MODEL_SINGLE_CHOICE = 0
    QUESTION_MODEL_MULTI_CHOICE = 1
    QUESTION_MODEL_SUBJECTIVE = 2
    QUESTION_MODEL_STEM = 3
    QUESTION_MODEL_VOCABULARY = 4  # since: 20.9.4

    QUESTION_MODEL_CHOICES = ((QUESTION_MODEL_SINGLE_CHOICE, u'单选'),
                              (QUESTION_MODEL_MULTI_CHOICE, u'多选'),
                              (QUESTION_MODEL_SUBJECTIVE, u'主观题'),
                              (QUESTION_MODEL_STEM, u'大题题干'),
                              (QUESTION_MODEL_VOCABULARY, u'词汇'))


logger = getLogger("venus")
TAG = "【word parser】"


# BR = Token(type=ParagraphType.TEXT, tags=['<br>'])
def roman_to_arabic(roman):
    roman_dict = {'I': 1, 'V': 5}
    total = 0
    previous_value = 0

    # 反向遍历字符串，以便正确处理IV(4)和类似的情况
    for i in range(len(roman) - 1, -1, -1):
        current_value = roman_dict[roman[i]]
        if current_value < previous_value:
            total -= current_value
        else:
            total += current_value
        previous_value = current_value

    return total


class SubjectQuestionHandlers:
    """ 不同学科特殊处理"""
    _subject_english_ids = (6, 14)  # 英语
    _subject_shuxue_ids = (1,)
    _subject_huaxue_ids = (2,)
    _subject_wuli_ids = (3,)
    _subject_shengwu_ids = (5,)
    _subject_yuwen_ids = (7, 13)
    _subject_lishi_ids = (8,)
    _subject_dili_ids = (9,)
    _subject_zhengzhi_ids = (10,)
    _MODEL_STEM_SYMBOLS_RES = [
        re.compile(r"\(([transitions二三四五六七八九十])\).*?本题共\d+小题"),
        re.compile(r"\(([一二三四五六七八九十])\).*?\d+分\)"),
    ]  # 题组题目：

    class SegmentHandler:
        """
        分节处理器
        """
        pre_handler = None
        post_handler = None
        question_model = None  # 对应的基础题型
        is_choice = None,
        subject_question_id = None,  # 学科题型
        question_model_type = None  # 1: 把正常题1. 当做题组小题

    def __init__(self):
        self._handler = self.SegmentHandler()
        self._blocks = []  # 块列表
        self._question = None  # 当前题token
        self._text = None  # 没有题情况下当前文本类型token; 一般用作容错处理
        self.this_is_segment_question = False  # 是否整个分节作为题组
        self.HANDLERS = {
            'auto_create_stem': self.auto_create_segment_question,  # 自动创建分节级题组题干
            # '5_from_7': lambda: self._5_from_7_handler(7, 5),
            # '4_from_6': lambda: self._5_from_7_handler(6, 4),
            # '5_from_5': lambda: self._5_from_7_handler(5, 5),
            # 'auto_create_blank': self._auto_create_questions_of_fill_blank
        }

    def auto_create_segment_question(self, token, no=''):
        """创建分段题组"""
        self._question = QuestionToken(no, token.tags)
        self._question.child_level = 0
        self.this_is_segment_question = True

    def is_question_english(self, token: Token):
        # 英语
        text = token.text()
        start_w_str = re.compile('^[A-F]篇.*?')
        if text in ['A', 'B', 'C', 'D', 'E', 'F'] or start_w_str.match(text):
            self.auto_create_segment_question(token)

    def is_question_shuxue(self, token: Token):
        # 数学
        return False

    def is_question_huaxue(self, token: Token):
        return False

    def is_question_wuli(self, token: Token):
        return False

    def is_question_shengwu(self, token: Token):
        return False

    def is_question_yuwen(self, token: Token):
        # (一)文言文阅读(本题共5小题,20分)
        # (二)古代诗歌阅读(本题共2小题,9分)
        chinese_to_arabic = {
            '一': 1,
            '二': 2,
            '三': 3,
            '四': 4,
            '五': 5,
            '六': 6,
            '七': 7,
            '八': 8,
            '九': 9,
            '十': 10
        }
        text = token.text()
        for re_exp in self._MODEL_STEM_SYMBOLS_RES:
            math = re_exp.match(text)
            if math:
                # no = math.group(1)
                # no = chinese_to_arabic.get(no) or no
                self.auto_create_segment_question(token, no='')
                break
        return False

    def is_question_lishi(self, token: Token):
        return False

    def is_question_dili(self, token: Token):
        return False

    def is_question_zhengzhi(self, token: Token):
        return False

    @property
    def get_subject_types_dict(self):
        _subject_types_dict = {
            self._subject_english_ids: self.is_question_english,  # 英语
            self._subject_shuxue_ids: self.is_question_shuxue,
            self._subject_huaxue_ids: self.is_question_huaxue,
            self._subject_wuli_ids: self.is_question_wuli,
            self._subject_shengwu_ids: self.is_question_shengwu,
            self._subject_yuwen_ids: self.is_question_yuwen,
            self._subject_lishi_ids: self.is_question_lishi,
            self._subject_zhengzhi_ids: self.is_question_zhengzhi,
        }
        return _subject_types_dict


class Lines2Blocks(SubjectQuestionHandlers):
    _IGNORE_SYMBOLS = [
        # 标题部分/预处理
        '绝密★启用前', '考试范围', '考试时间', '注意事项', '答题前填写', '将答案填写在', '填写在答题卡上', '答题卡上',
        '答题卡的', '试卷副标题',
        '考号:', '学号:', '学籍号：', '考号：', '学号：', '学籍号：',
        # 过程中
        '评卷人', '阅卷人', '第I卷', '第II卷', '第III卷', '第V卷', '第VI卷',
    ]
    _SEGMENT_SYMBOLS = ['一', '二', '三', '四', '五', '六', '七', '八', '九', '十']
    _SEGMENT_SYMBOLS2 = ['、', ' ', '.', '．']
    _IGNORE_WORDS = ['评卷人', '阅卷人', '得分', '班级:', '\n']  # 尝试分析分节时需要过滤的文字，支持分节前有给分表
    _NO_RES = [re.compile(r'^\d+[.．:：]'), ]  # 普通题目：

    _PROPER_SEGMENT_FLAGS = ['参考答案', '参考答案:', '参考答案：', '【参考答案】']  # 参考答案分节标志

    # 题组标记
    _MODEL_STEM_SYMBOLS = ['以下小题', '下面小题', '下列问题', '完成下列要求', '完成下列小题', '回答以下题',
                           '阅读下面的文段']

    # 小题: (1) 1)
    _CHILD_REGS = [re.compile(r'^(（\d+）)(\.*)'), re.compile(r'^(\(\d+\))(\.*)'), re.compile(r'^(\d+[)|）])(\.*)')]
    # _TIME_RES = re.compile(r'^\d+[:：]\d+')  # 时间：
    _TIME_RES = re.compile(r'^(?:[01]\d|2[0-3])[:：][0-5]\d.*?')  # 时间：

    _PROPER_FLAGS = ['【答案】', '答案 ']
    _ANALYSIS_SYMBOLS = ['【解析】', '解析 ']
    _OPTION_FLAG_REGS = [re.compile(r'^[ABCDEFGHIJKLMN][.．]')]
    _OPTIONS_REG = re.compile(r'([ABCDEFGHIJKLMN][.．])+')
    _SCORE = []  # 分值分析
    ROMAN_NUMERALS = ['I', 'II', 'III', 'IV', 'V', 'VI', 'VII', 'VIII', 'IX']  # 罗马数字

    PARSE_PHASE_TITLE = 0
    PARSE_PHASE_QUESTIONS = 1
    PARSE_PHASE_ANSWER = 2

    def __init__(self, subject_id):
        super().__init__()
        self.subject_id = subject_id
        self.segment_question_end_no = 0  # 章节题组结束题号

    def text_is_segment_question(self, token: Token, _question_option_end):
        # 选择题下面跟着没有题号题干甄别
        if (
                len(token.text()) >= len(_question_option_end.text()) and
                _question_option_end.option in ['D.', 'E.', 'F.', 'G.', 'H.']
        ):
            return True

        re_compile = re.compile(r'.*?完成下列任务[: ]')
        if re_compile.match(token.text()):
            return True
        return False

    def _segment_pre_handler(self, token: Token):
        """
        分段前置处理器
        :return:
        """

        self._handler.pre_handler = None
        self._handler.post_handler = None
        self._handler.question_model = None
        self._handler.subject_question_id = None
        text = token.text()
        # # text = PQ(token.text()).text()
        # # data = get_subject_question_type(self.subject_id, text)
        # # if data:
        # #     self._handler.pre_handler = data['pre']
        # #     self._handler.post_handler = data['post']
        # #     # 如果是选择题，先假定单选。处理答案时，或处理分节时再细化/具体化
        # #     self._handler.question_model = QuestionEnum.QUESTION_MODEL_SINGLE_CHOICE if data[
        # #         'is_choice'
        # #     ] else QuestionEnum.QUESTION_MODEL_SUBJECTIVE
        # #     self._handler.is_choice = data['is_choice']
        # #     self._handler.subject_question_id = data['id']
        # # else:
        #     self._handler.question_model = None
        # if self._handler.pre_handler:
        #     handler = self.HANDLERS.get(self._handler.pre_handler, None)
        #     if handler:
        #         handler(token)
        #         return Token
        return False

    def _get_question_no(self, text: str, tags: List[OpticalLine | str], first_tag_matched: bool = True):
        """
        获取题号，和已经处理过的tags
        :param first_tag_matched: 文本是否存在标签[True:不存在标签]
        :param text:
        :param tags:
        :return:
        """
        for re_exp in self._NO_RES:
            match = re_exp.match(text)
            if match:
                if self._TIME_RES.match(text):
                    # 是时间格式，混淆识别为题号
                    continue
                no = text[:match.end() - 1]

                if first_tag_matched:
                    if len(text) == match.end():
                        tags.pop(0)
                    else:
                        if isinstance(tags[0], OpticalLine):
                            tags[0].text = tags[0].text[match.end():]
                        else:
                            tags[0] = tags[0][match.end():]
                return no, tags

        return None, None

    def parse_token(self, p: OpticalLine) -> Optional[Token]:
        """
        分析文本类型
        题干：
            extra包含: no——题号
        :param p:
        :return:[类型,标签, 是否是从第一个标签中匹配的]
        """
        text = p.text.strip()
        if not text:  # 空白
            return None
        tags = [p]

        # 看是否是直接忽略的内容
        for symbol in self._IGNORE_SYMBOLS:
            if symbol in text:
                return IgnoreToken(ParagraphType.IGNORE, None)

        # 判断是否是分段
        for symbol in self._SEGMENT_SYMBOLS:
            if text.startswith(symbol) and text[1:2] in self._SEGMENT_SYMBOLS2:
                token = Token(ParagraphType.SEGMENT, tags)
                return token
            _r = re.compile(r'[?]?新题型\d')
            if _r.match(text):
                token = Token(ParagraphType.SEGMENT, tags=tags)
                return token

        # 判断是否是二级分段
        for stem_re_exp in self._MODEL_STEM_SYMBOLS_RES:
            match = stem_re_exp.match(text)
            if match:
                token = Token(ParagraphType.SEGMENT, tags)
                return token

        # 罗马数字转化:
        roman_numerals = ['I', 'II', 'III', 'IV', 'V', 'VI', 'VII', 'VIII', 'IX']
        for _lm_num in roman_numerals:
            if text.startswith(f'{_lm_num}.'):
                text = str(roman_to_arabic(_lm_num)) + text[len(_lm_num):]

        # 判断是否是题干
        no, _tags = self._get_question_no(text, tags)
        # no = self._get_question_no(text)
        if no:
            token = QuestionToken(no, tags)
            token.child_level = 1
            # 解析阶段，保留最后的题号

            return token
        # # 判断是否是小题
        for re_exp in self._CHILD_REGS:
            match = re_exp.match(text)
            if match:

                no = match.group(0)
                if no.startswith('(') or no.startswith('（'):
                    no = no[1:]
                if no.endswith(")") or no.endswith("）"):
                    no = no[:-1]
                token = QuestionToken(no, tags)
                token.is_child = Token
                token.child_level = 2
                return token
        # 判断是否是选项

        _text = text.upper()  # ocr有可能会大小写识别错误
        for re_exp in self._OPTION_FLAG_REGS:
            match = re_exp.match(_text)
            if match and not text.startswith('I am'):
                token = OptionToken(None, tags)
                token.option = match.group(0)
                return token  # 全部返回，便于解析一行多选项
        if len(text) <= 8 and text.endswith('题图'):
            # 捕获图片中的字并置空防止影响后续解析
            tags[-1].text = ''
            return Token(ParagraphType.TEXT, tags)
        token = Token(ParagraphType.TEXT, tags)
        return token

    @staticmethod
    def _replace_chinese_punctuation(lines):
        # 部分中文符号替换成英文符号
        for line in lines:
            line.text = replace_chinese_punctuation(line.text)

    def _text_handlers(self, token: Token):
        """
        文本类型前置处理器:
            分节下没有题干类型则检查字符串是否是题组标识;如果是,创建分节级题组
        """
        if not token:
            return
        if self._question:
            # 分节下已经存在题干不在创建
            return
        for _model in self._MODEL_STEM_SYMBOLS:
            if _model in token.text():
                self.auto_create_segment_question(token)
        #
        for subject_ids, subject_type_func in self.get_subject_types_dict.items():
            if self.subject_id in subject_ids:
                subject_type_func(token)
                break

    def _push_blocks(self, token):
        self._blocks.append(token)

    def _get_token_blocks(self, lines: List[OpticalLine]) -> List[Token | QuestionToken]:
        """
        根据每一行的属性按题归类并加入列表
        """
        # self._handler

        self._question = None
        for line in lines:
            # line转为token
            _token = self.parse_token(line)
            print('_token:', _token.print)
            if not _token:
                continue
            if _token.type in [ParagraphType.SEGMENT]:  # 【分段，】
                # 1. 把self._question 存入self._blocks 并置空
                # 2. 判断是否是分节题组,如果是把当前分节的普通题目变成小题
                # 1.
                if self._question:
                    self._push_blocks(self._question)
                    self._question = None
                # 2.
                if not self._segment_pre_handler(_token):
                    self._text_handlers(_token)
                # 是题组也需要把分节加入_blocks
                self._push_blocks(_token)
            elif isinstance(_token, QuestionToken) and _token.type == ParagraphType.STEM:  # 题干
                # 1.如当前分节是题组则把普通题标记为小题
                # 2. 把上self._question入栈;并把当前token赋值到self._question
                if self._question:
                    # 如果当前分节是题组则把当前题加入题组
                    if self.this_is_segment_question:
                        if not self.segment_question_end_no:
                            # 8-9题
                            end_text_compile = re.compile(r'.*?完成\d+-(\d+)题.*?')
                            end_text_match = end_text_compile.match(self._question.text()[-10:])
                            if end_text_match:
                                self.segment_question_end_no = int(end_text_match.group(1))
                        this_no = int(_token.no) if _token.no.isdigit() else 0
                        if self.segment_question_end_no and this_no > self.segment_question_end_no:
                            self.segment_question_end_no = 0
                        else:
                            _token.is_child = True
                    self._push_blocks(self._question)
                self._question = _token
                continue

            elif isinstance(_token, OptionToken):  # 文本，选项
                # 如果是选项附属token则追加到上一个主token
                if self._question:
                    # 1.如果有self._question:追加到self._question
                    self._question.tags.append(_token.tags[0])
                    if not self._question.options:
                        self._question.options = []
                    self._question.options.append(_token)
                else:
                    # 2.如果没self._question:判断当前文本是否是隐形题组
                    self._text_handlers(_token)
            elif isinstance(_token, Token) and _token.type == ParagraphType.TEXT:
                #  如果是普通文本附属token则追加到上一个主token
                if self._question:
                    # 1.
                    if self._question.options:
                        # # 上一个是选项: 长度大于选项长度则创建题目
                        _question_option_end = self._question.options[-1]
                        if self.text_is_segment_question(_token, _question_option_end):
                            self._push_blocks(self._question)
                            self.auto_create_segment_question(_token, no='')
                            continue
                        _question_option_end.tags.append(_token.tags[0])
                    self._question.tags.append(_token.tags[0])
                else:
                    # 2.
                    self._text_handlers(_token)
            else:
                # 其他类型
                pass

        if self._question:
            self._blocks.append(self._question)
        for block in self._blocks:
            print(f":block:{block.print} ")
        return self._blocks

    def merge_box(self, _blocks):
        """
             合并每一道题的box
        """

        for index, q in enumerate(_blocks):
            _page_boxes = q.get_bounding_boxes()

            q.page_boxes = [{'url': url, 'boxes': boxes} for url, boxes in _page_boxes.items()]
            q.boxes = [boxes for url, boxes in _page_boxes.items()]
            if q.children:
                for sub_q in q.children:
                    sub_q.children = []
                    _sub_page_boxes = sub_q.get_bounding_boxes()
                    sub_q.page_boxes = [{'url': url, 'boxes': boxes} for url, boxes in _sub_page_boxes.items()]
                    sub_q.boxes = [boxes for url, boxes in _sub_page_boxes.items()]
            else:
                q.children = []
        return _blocks

    def get_question_blocks(self, lines):
        # 入口
        # # 替换部分中文符号为英文符合
        self._replace_chinese_punctuation(lines)
        # 获取token块list
        _blocks = self._get_token_blocks(lines)
        # 合并块box
        question__blocks = self.merge_box(_blocks)
        return question__blocks




